#include "ViewerCamera.h"

const static float STEP_SCALE = 0.1f;
const static int MARGIN = 75;

void CViewerCamera::Update()
{
	const Vector3f Vaxis(0.0f, 1.0f, 0.0f);

    // Rotate the view vector by the horizontal angle around the vertical axis
    Vector3f View(1.0f, 0.0f, 0.0f);
    View.Rotate(m_AngleH, Vaxis);
    View.Normalize();

    // Rotate the view vector by the vertical angle around the horizontal axis
    Vector3f Haxis = Vaxis.Cross(View);
    Haxis.Normalize();
    View.Rotate(m_AngleV, Haxis);
    View.Normalize();

    m_Direction = View;
    m_Direction.Normalize();

    m_Up = m_Direction.Cross(Haxis);
    m_Up.Normalize();

	Matrix4f CamTranslation, CamRotation;
	CamTranslation.InitTranslationTransform(-m_Position.x , -m_Position.y, -m_Position.z);
	CamRotation.InitCameraTransform(m_Direction, m_Up );
	m_ViewTransform = CamRotation * CamTranslation;
	m_ProjectionTransform.InitPersProjTransform(m_FOV, m_Width, m_Height, m_zNear, m_zFar);
}
void CViewerCamera::UpdateMousePos(int x, int y)
{
	m_mousePos.x = x;
	m_mousePos.y = y;
}
void CViewerCamera::Init(float FOV, float Width, float Height, float zNear, float zFar, const Vector3f& FocusPoint, const Vector3f& Position, const Vector3f& Up)
{
	m_FocusPoint = FocusPoint;
	m_Position = Position;
	m_Direction = (FocusPoint - Position).Normalize();
	m_Up = Up;
	SetProjectionParams(FOV, Width, Height, zNear, zFar);

	Vector3f HTarget(m_Direction.x, 0.0, m_Direction.z);
    HTarget.Normalize();

    if (HTarget.z >= 0.0f)
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = 360.0f - ToDegree(asin(HTarget.z));
        }
        else
        {
            m_AngleH = 180.0f + ToDegree(asin(HTarget.z));
        }
    }
    else
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = ToDegree(asin(-HTarget.z));
        }
        else
        {
            m_AngleH = 90.0f + ToDegree(asin(-HTarget.z));
        }
    }

    m_AngleV = - ToDegree(asin(m_Direction.y));

    m_OnUpperEdge = false;
    m_OnLowerEdge = false;
    m_OnLeftEdge = false;
    m_OnRightEdge = false;
    m_mousePos.x = m_Width / 2;
    m_mousePos.y = m_Height / 2;
	m_stop = false;
	m_OrbitAngleU = M_PI;
	m_OrbitAngleV = 0;
	m_POIMesh = new CPointOfInterest(FocusPoint);
	m_bMoveForawrd = false;
    glutWarpPointer(m_mousePos.x, m_mousePos.y);
}

void CViewerCamera::SetProjectionParams(float FOV, float Width, float Height, float zNear, float zFar)
{
	m_FOV = FOV;
	m_Width = Width;
	m_Height = Height;
	m_zNear = zNear;
	m_zFar = zFar;
}

void CViewerCamera::Move(unsigned char key)
{
	const float StepSize = 0.1f;
	switch (key)
	{
	case 'a':
	{
		Vector3f Left = m_Direction.Cross(m_Up);
		Left.Normalize();
		Left *= StepSize;
		m_Position += Left;
		m_FocusPoint += Left;
		break;
	}
	case 'd':
	{
		Vector3f Right = m_Up.Cross(m_Direction);
		Right.Normalize();
		Right *= StepSize;
		m_Position += Right;
		m_FocusPoint += Right;
		break;
	}
	case 'w':
	{
		m_Position += (m_Direction * StepSize);
		m_FocusPoint += (m_Direction * StepSize);
		break;
	}
	case 's':
	{
		m_Position += (m_Direction * -StepSize);
		m_FocusPoint += (m_Direction * -StepSize);
		break;
	}
	case 'z':
	{
		m_Position += (m_Up * StepSize);
		m_FocusPoint +=  (m_Up * StepSize);
		break;
	}
	case 'x':
	{
		m_Position += (m_Up* -StepSize);
		m_FocusPoint +=  (m_Up * -StepSize);
		break;
	}
	default:
		break;
	}

	Update();
}

void CViewerCamera::Move(int x, int y)
{
	const float StepSize = 0.03f;

	const int DeltaX = x - m_mousePos.x;
	const int DeltaY = y - m_mousePos.y;

	m_mousePos.x = x;
	m_mousePos.y = y;

	if (DeltaX < 0)
	{
			Vector3f Left = m_Direction.Cross(m_Up);
			Left.Normalize();
			Left *= -DeltaX * StepSize;
			m_Position += Left;
			m_FocusPoint += Left;
	}
	else if (DeltaX > 0)	
	{
		Vector3f Right = m_Up.Cross(m_Direction);
		Right.Normalize();
		Right *= DeltaX * StepSize;
		m_Position += Right;
		m_FocusPoint += Right;
	}

	Vector3f Dir = m_bMoveForawrd ? m_Direction : m_Up;

	m_Position += (Dir * -DeltaY * StepSize);
	m_FocusPoint += (Dir * -DeltaY * StepSize);

	Update();
}

Vector3f CViewerCamera::GetFocusPoint() const
{
	return m_FocusPoint;
}

void CViewerCamera::Orbit(int x, int y, Vector3f Center)
{
	const int DeltaX = x - m_mousePos.x;
	const int DeltaY = y - m_mousePos.y;
	m_mousePos.x = x;
	m_mousePos.y = y;
	float u = -(float)DeltaX / 500.0f;
	float v = -(float)DeltaY / 500.0f;
	m_OrbitAngleU += u;
	m_OrbitAngleV += v;
	float Radius = (Center - m_Position).Norm();
	m_Position = Vector3f(Radius * sin(m_OrbitAngleU) * cos(m_OrbitAngleV) + Center.x, Center.y + Radius * sin(m_OrbitAngleV), Radius * cos(m_OrbitAngleU) * cos(m_OrbitAngleV) + Center.z);
	m_AngleH += ToDegree(u);
	m_AngleV += ToDegree(v);

	Update();
}
void CViewerCamera::OnMouse(int x, int y)
{
    const int DeltaX = x - m_mousePos.x;
    const int DeltaY = y - m_mousePos.y;

    m_mousePos.x = x;
    m_mousePos.y = y;

    m_AngleH += (float)DeltaX / 20.0f;
    m_AngleV += (float)DeltaY / 20.0f;

    if (DeltaX == 0) {
        if (x <= MARGIN) {
            m_OnLeftEdge = true;
        }
        else if (x >= (m_Width - MARGIN)) {
            m_OnRightEdge = true;
        }
    }
    else {
        m_OnLeftEdge = false;
        m_OnRightEdge = false;
    }

    if (DeltaY == 0) {
        if (y <= MARGIN) {
            m_OnUpperEdge = true;
        }
        else if (y >= (m_Height - MARGIN)) {
            m_OnLowerEdge = true;
        }
    }
    else {
        m_OnUpperEdge = false;
        m_OnLowerEdge = false;
    }

    Update();
}

void CViewerCamera::OnRender(bool bDrawFocusPoint)
{
	bool ShouldUpdate = false;
	const float RotateFactor = 0.4f;
	if (m_OnLeftEdge) {
		m_AngleH -= RotateFactor;
		ShouldUpdate = true;
	}
	else if (m_OnRightEdge) {
		m_AngleH += RotateFactor;
		ShouldUpdate = true;
	}

	if (m_OnUpperEdge) {
		if (m_AngleV > -90.0f) {
			m_AngleV -= RotateFactor;
			ShouldUpdate = true;
		}
	}
	else if (m_OnLowerEdge) {
		if (m_AngleV < 90.0f) {
			m_AngleV += RotateFactor;
			ShouldUpdate = true;
		}
	}

	if (ShouldUpdate) 
	{
		Update();
	}
	if (bDrawFocusPoint)
	{
		m_POIMesh->Draw(m_ProjectionTransform*m_ViewTransform, m_FocusPoint);
	}
}

void CViewerCamera::SetSpecialPositionTop()
{
	VISIO_ASSERT(0);
	Init(m_FOV, m_Width, m_Height, m_zNear, m_zFar, Vector3f(0.013848214, -0.93572277, 0.35246447), Vector3f(-0.031964093f, 1.8757534f, -0.25262988)  , Vector3f(0.036735900, 0.35273641, 0.93500137) );
	Update();
}

void CViewerCamera::SetSpecialPositionNormal()
{
	VISIO_ASSERT(0);
	Init(m_FOV, m_Width, m_Height, m_zNear, m_zFar, Vector3f(0,0,1), Vector3f(0,0,-1), Vector3f(0,1,0));
	Update();
}

void CViewerCamera::Zoom(int dir)
{
	if (dir > 0)
	{
		m_FOV -= 1;
	}
	else
	{
		m_FOV += 1;
	}

	Update();
}

void CViewerCamera::SetMoseMoveForward( bool b )
{
	m_bMoveForawrd = b;
}
